package gu.simplemq.redis;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import gu.simplemq.Constant;
import gu.simplemq.json.BaseJsonEncoder;
import gu.simplemq.pool.BaseMQPool;
import gu.simplemq.utils.TypeConversionSupport;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisException;

/**
 * 延迟初始化的 {@link Jedis} 资源池（线程安全）<br>
 * @author guyadong
 *
 */
public class JedisPoolLazy extends BaseMQPool<Jedis> implements Constant{
	/** {@link JedisPoolLazy} 初始化参数名 */
	public static enum PropName{
		/** 线程配置参数对象,类型:{@link JedisPoolConfig} */jedisPoolConfig(JedisPoolConfig.class),
		/** 主机名,类型: {@link String}*/host,
		/** 端口号,类型: {@link Integer} */port(Integer.class),
		/** REDIS密码,类型: {@link String} */password,
		/** 数据库ID,类型: {@link Integer}  */database(Integer.class),
		/** 访问超时(毫秒),类型: {@link Integer} */timeout(Integer.class),
		/** 访问地址,类型: {@link URI}*/uri(URI.class);
		public final Class<?>type;
		private PropName(){
			this(String.class);
		}
		private PropName(Class<?> type){
			this.type = type;
		}
		public Object parse(Object input){
			if(input == null || type.isInstance(input)){
				return input;
			}
			if(Number.class.isAssignableFrom(type) && input instanceof Number){
				if(Integer.class.equals(type)){
					return ((Number)input).intValue();
				}
				if(Long.class.equals(type)){
					return ((Number)input).longValue();
				}
				if(Short.class.equals(type)){
					return ((Number)input).shortValue();
				}
				if(Byte.class.equals(type)){
					return ((Number)input).byteValue();
				}
				if(Float.class.equals(type)){
					return ((Number)input).floatValue();
				}
				if(Double.class.equals(type)){
					return ((Number)input).doubleValue();
				}
			}
			if(input instanceof String){
				return parse((String)input);
			}
			throw new UnsupportedOperationException("INVALID input type " + input.getClass().getName());
		}
		public Object parse(String input){
			if(String.class.equals(type)){
				return input;
			}
			if(Integer.class.equals(type)){
				return null == input ? null : Integer.valueOf(input);
			}
			if(URI.class.equals(type)){
				return null == input ? null : URI.create(input);
			}
			if(JedisPoolConfig.class.equals(type)){
				return null == input ? null : BaseJsonEncoder.getEncoder().fromJson(input, JedisPoolConfig.class);
			}
			throw new UnsupportedOperationException("UNSUPPORTED type " + type.getName());
		}
		public String toValueString(Object value){
			if(value instanceof JedisPoolConfig){
				return BaseJsonEncoder.getEncoder().toJsonString(value);
			}			
			return null == value ? null : value.toString();
		}
	}
	
	public static final JedisPoolConfig DEFAULT_CONFIG = new JedisPoolConfig() {
		{
			setMaxTotal(Runtime.getRuntime().availableProcessors());
		}
	};
	/** 
	 * redis缺省连接参数<br>
	 * 这里没有使用guava的ImmutableMap，因为HashMap允许Value为null, ImmutableMap不允许 
	 **/
	public static final Map<PropName, Object> DEFAULT_PARAMETERS = Collections.unmodifiableMap(new HashMap<PropName, Object>() {
		private static final long serialVersionUID = 1L;
		{
			put(PropName.jedisPoolConfig, DEFAULT_CONFIG);
			put(PropName.host, Protocol.DEFAULT_HOST);
			put(PropName.port, Protocol.DEFAULT_PORT);
			put(PropName.password, null);
			put(PropName.database, Protocol.DEFAULT_DATABASE);
			put(PropName.timeout, Protocol.DEFAULT_TIMEOUT);
		}
	});
	
	private final Map<PropName,Object> parameters;
	
	private volatile JedisPool pool;
	private static final boolean jmxEnable = isJmxEnable();

	/**
	 * 构造方法，用于{@link gu.simplemq.pool.NamedMQPools#createPool(Properties)}反射调用创建实例
	 * 
	 * @param props
	 * @since 2.4.0
	 */
	public JedisPoolLazy (Properties props) {
		this(JedisUtils.asRedisParameters(props));
	}
	protected JedisPoolLazy (Map<PropName,Object> props) {
		super(TypeConversionSupport.asProperties(JedisUtils.asMqParameters(JedisUtils.initParameters(props))), JedisUtils.getCanonicalURI(props));
		this.parameters = JedisUtils.initParameters(props);
	}
	
	public Map<PropName, Object> getParameters() {
		return new HashMap<PropName,Object>(parameters);
	}
	private JedisPool createPool(){
		JedisPool pool;
		int timeout = (Integer)parameters.get(PropName.timeout);
		URI uri = this.getCanonicalURI();
		JedisPoolConfig config = (JedisPoolConfig) parameters.get(PropName.jedisPoolConfig);
		// 指定jmxEnable,解决 android 下异常，android不支持JMX
		config.setJmxEnabled(jmxEnable);
		pool = new JedisPool(
				config,
				uri, 
				timeout);
		logger.info("jedis pool initialized(连接池初始化)  {} timeout : {} ms",uri,timeout);
		return pool;
	}

	@Override
	public Jedis borrow(){
		// double-checked locking
		if(null == pool){
			synchronized (this){
				if(null == pool){
					pool = createPool();
				}
			}
		}
		try {
			return pool.getResource();
		} catch (JedisException e) {
			throw new MQPoolException(e);
		}
    }
    
	@Override
    public void release(Jedis r) {
        if (r != null){
        	try {
        		r.close();
			} catch (JedisException e) {
				throw new MQPoolException(e);
			}
        }
    }
	/**
	 * 判断JVM是否支持JMX，android下返回{@code false}
	 */
	private static boolean isJmxEnable(){
		try{
			Class.forName("java.lang.management.ManagementFactory");
			return true;
		}catch(ClassNotFoundException e){
			return false;
		}
	}
	
	@Override
	public void close(){
		// double check
		if(pool != null){
			synchronized (this) {
				if(pool != null){
					logger.info("discard jedis pool: {}",this);
					try {
						pool.close();
						pool = null;
						closed = true;
					} catch (JedisException e) {
						throw new MQPoolException(e);
					}
				}
			}
		}
	}
}
